/******************************************************
*  Copyright(c) pages.ilovezxl.top
*  FileName : can_linux.c
*  Author : rivulet.dave
*  Description : can Linux平台实现
*  Data : 2020-08-05
******************************************************/

/*********************************************************************
 * INCLUDES
 */

#include "stdrope.h"

#include "hallib.h"
#include "halagent.h"

#if (HAL_CAN == _ENABLE && HAL_PRODUCER == _HAL_LINUX_)

#include <linux/can.h>
#include <linux/can/raw.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>

#include "log.h"
#include "can_linux.h"

/*********************************************************************
 *  EXTERNAL VARIABLES
 */

/*********************************************************************
 * MACROS
 */

#define MOD_NAME                TOSTR(HW_CAN)

/* error class (mask) in can_id */
#define CAN_ERR_TX_TIMEOUT   0x00000001U /* TX timeout (by netdevice driver) */
#define CAN_ERR_LOSTARB      0x00000002U /* lost arbitration    / data[0]    */
#define CAN_ERR_CRTL         0x00000004U /* controller problems / data[1]    */
#define CAN_ERR_PROT         0x00000008U /* protocol violations / data[2..3] */
#define CAN_ERR_TRX          0x00000010U /* transceiver status  / data[4]    */
#define CAN_ERR_ACK          0x00000020U /* received no ACK on transmission */
#define CAN_ERR_BUSOFF       0x00000040U /* bus off */
#define CAN_ERR_BUSERROR     0x00000080U /* bus error (may flood!) */
#define CAN_ERR_RESTARTED    0x00000100U /* controller restarted */

/*********************************************************************
 * TYPES
 */

typedef struct _can_linux_cb
{
    HAL_OPER_S stBaseObj;  //须在第一个位置
    const char *pName;
    int32_t iFd;
} CAN_LINUX_CB_S;

/*********************************************************************
 * FUNCTIONS DECLARE
:w
*/

static hd_t CAN_OpenByLinux(uint32_t uiIdx);
static int32_t CAN_CloseByLinux(hd_t hModuleDescriptor);
static int32_t CAN_ReadByLinux(hd_t hModuleDescriptor, uint8_t *pUserBuf, uint32_t uiSize, uint32_t uiFlag);
static int32_t CAN_WriteByLinux(hd_t hModuleDescriptor, const uint8_t *pUserData, uint32_t uiSize, uint32_t uiFlag);
static int32_t CAN_CtrlByLinux(hd_t hModuleDescriptor, uint32_t uiCmdKey, void *pValue);
static int32_t CAN_LockByLinux(hd_t hModuleDescriptor, uint32_t uiFlag);
static int32_t CAN_UnlockByLinux(hd_t hModuleDescriptor, uint32_t uiFlag);

/*********************************************************************
 * VARIABLES
 */

static HAL_OPER_S s_stCanBaseLinuxObj;
static CAN_LINUX_CB_S s_astCanLinuxCb[HAL_CAN_NUM];
static char *s_acCanNameList[] = HAL_CAN_NAMES;

/*********************************************************************
 * FUNCTIONS
 */

/**
 *   @Description: CAN初始化相关接口，指针
 *   @param : NA
 *   @return: 结构体指针
 */
HAL_OPER_S *CAN_ObjNewByLinux(void)
{
    s_stCanBaseLinuxObj.pThis = &s_stCanBaseLinuxObj;
    s_stCanBaseLinuxObj.pfOpen = CAN_OpenByLinux;
    s_stCanBaseLinuxObj.pfClose = CAN_CloseByLinux;
    s_stCanBaseLinuxObj.pfLock = CAN_LockByLinux;
    s_stCanBaseLinuxObj.pfUnlock = CAN_UnlockByLinux;
    s_stCanBaseLinuxObj.pfRead = CAN_ReadByLinux;
    s_stCanBaseLinuxObj.pfWrite = CAN_WriteByLinux;
    s_stCanBaseLinuxObj.pfReadOnce = NULL;
    s_stCanBaseLinuxObj.pfWriteOnce = NULL;
    s_stCanBaseLinuxObj.pfCtrl = CAN_CtrlByLinux;
    
    for (uint32_t i = 0; i < HAL_CAN_NUM; i++)
    {
        s_astCanLinuxCb[i].pName = s_acCanNameList[i];
    }
    return &s_stCanBaseLinuxObj;
}

/**
 *   @Description: CAN去初始化、退出
 *   @param : NA
 *   @return: _OK/_ERR
 */
int32_t CAN_ObjDeleteByLinux(void)
{
    return _OK;
}

static hd_t CAN_OpenByLinux(uint32_t uiIdx)
{
    int32_t iRet;
    int32_t iSockFd = 0;
    can_err_mask_t err_mask;
    struct sockaddr_can stSockAddr = {0};
    struct ifreq stIfReq = {0};
    int iLoopBack = 0; /* 0 = disabled, 1 = enabled (default) */
    int iRecvOwnMsgs = 0; /* 0 = disabled (default), 1 = enabled */

    if (HAL_CAN_NUM <= uiIdx )
    {
        LOG_ERR(MOD_NAME, "can idx:%s is invalid.\n", uiIdx);
        return NULL;
    }    
    
    iSockFd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (0 > iSockFd)
    {
        LOG_ERR(MOD_NAME, "socket can error: %s", strerror(errno));
        return NULL;
    }

    strcpy(stIfReq.ifr_name, s_astCanLinuxCb[uiIdx].pName);
    ioctl(iSockFd, SIOCGIFINDEX, &stIfReq);

    stSockAddr.can_family   = PF_CAN;
    stSockAddr.can_ifindex = stIfReq.ifr_ifindex; 

    iRet = bind(iSockFd, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr));
    if (0 > iRet)
    {
        LOG_ERR(MOD_NAME, "can bind error: %s", strerror(errno));
        goto FAIL;
    }

    // set socket option with CAN_RAW_LOOPBACK\CAN_RAW_RECV_OWN_MSGS\CAN_RAW_FILTER
    err_mask = ( CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | CAN_ERR_BUSOFF);
    setsockopt(iSockFd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask));
    setsockopt(iSockFd, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &iLoopBack, sizeof(iLoopBack));
    setsockopt(iSockFd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &iRecvOwnMsgs, sizeof(iRecvOwnMsgs));

    LOG_DBG(MOD_NAME, "can:%s open, idx:%d, fd:%d\n", s_astCanLinuxCb[uiIdx].pName, uiIdx, iSockFd);

    s_astCanLinuxCb[uiIdx].iFd = iSockFd;
    s_astCanLinuxCb[uiIdx].stBaseObj = s_stCanBaseLinuxObj;
    s_astCanLinuxCb[uiIdx].stBaseObj.pThis = &s_astCanLinuxCb[uiIdx];
    return &s_astCanLinuxCb[uiIdx];

FAIL:
    if (iSockFd > 0)
    {
        close(iSockFd);
    }

    return NULL;
}

static int32_t CAN_CloseByLinux(hd_t hModuleDescriptor)
{
    CAN_LINUX_CB_S *pstCanLinuxCb = (CAN_LINUX_CB_S *)hModuleDescriptor;

    if (!pstCanLinuxCb)
    {
        return _ERR;
    }

    if (0 < pstCanLinuxCb->iFd)
    {
        close(pstCanLinuxCb->iFd);
        pstCanLinuxCb->iFd = -1;
    }

    LOG_DBG(MOD_NAME, "can:%s close.\n", pstCanLinuxCb->pName);
    return _OK;
}

static int32_t CAN_ReadByLinux(hd_t hModuleDescriptor, uint8_t *pUserBuf, uint32_t uiSize, uint32_t uiFlag)
{
    int32_t iRet;
    int32_t iCanFlags;
    CAN_LINUX_CB_S *pstCanLinuxCb = (CAN_LINUX_CB_S *)hModuleDescriptor;
    CAN_MSG_S *pstCanMsg = (CAN_MSG_S *)pUserBuf;
    struct can_frame stCanFrame = { 0 };

    if (!pstCanLinuxCb || !pstCanMsg)
    {
        return _ERR;
    }

    if (0 > pstCanLinuxCb->iFd)
    {
        return _ERR;
    }

    iCanFlags = fcntl(pstCanLinuxCb->iFd, F_GETFL, 0);
    if (iCanFlags < 0)
    {
        LOG_ERR(MOD_NAME, "can fcntl get fail.\n");
        return _ERR;
    }

    if (_GET_BIT(uiFlag, H_FLAG_BLOCK))
    {
        _CLEAR_BIT(iCanFlags, O_NONBLOCK);
    }
    else
    {
        _SET_BIT(iCanFlags, O_NONBLOCK);
    }

    if (fcntl(pstCanLinuxCb->iFd, F_SETFL, iCanFlags) < 0)
    {
        LOG_ERR(MOD_NAME, "can fcntl set fail.\n");
        return _ERR;
    }

    iRet = read(pstCanLinuxCb->iFd, &stCanFrame, sizeof(struct can_frame));
    if (0 > iRet)
    {
        LOG_ERR(MOD_NAME, "can:%s read fail:%s\n", pstCanLinuxCb->pName, strerror(errno));
        return _ERR;
    }

    pstCanMsg->uiCanId = stCanFrame.can_id;
    pstCanMsg->uiLen = stCanFrame.can_dlc;
    for (uint32_t i=0; i<CAN_MSG_LEN; i++)
    {
        pstCanMsg->aucData[i] = stCanFrame.data[i];
    }
    return sizeof(CAN_MSG_S);
}

static int32_t CAN_WriteByLinux(hd_t hModuleDescriptor, const uint8_t *pUserData, uint32_t uiSize, uint32_t uiFlag)
{
    int32_t iRet;
    CAN_MSG_S *pstCanMsg = (CAN_MSG_S *)pUserData;
    struct can_frame stCanFrame = { 0 };
    CAN_LINUX_CB_S *pstCanLinuxCb = (CAN_LINUX_CB_S *)hModuleDescriptor;

    if (!pstCanLinuxCb || !pstCanMsg)
    {
        return _ERR;
    }

    if (0 > pstCanLinuxCb->iFd)
    {
        return _ERR;
    }

    stCanFrame.can_id = pstCanMsg->uiCanId;
    stCanFrame.can_dlc = pstCanMsg->uiLen;
    for (uint32_t i=0; i<CAN_MSG_LEN; i++)
    {
        stCanFrame.data[i] = pstCanMsg->aucData[i];
    }
    iRet = write(pstCanLinuxCb->iFd, &stCanFrame, sizeof(struct can_frame));
    if (0 > iRet)
    {
        LOG_ERR(MOD_NAME, "can:%s read fail:%s\n", pstCanLinuxCb->pName, strerror(errno));
        return _ERR;
    }
    return sizeof(CAN_MSG_S);
}

static int32_t CAN_CtrlByLinux(hd_t hModuleDescriptor, uint32_t uiCmdKey, void *pValue)
{
    int32_t iRet = _ERR;
    CAN_LINUX_CB_S *pstCanLinuxCb = (CAN_LINUX_CB_S *)hModuleDescriptor;

    if (!pstCanLinuxCb)
    {
        return _ERR;
    }

    if (0 > pstCanLinuxCb->iFd)
    {
        return _ERR;
    }

    // 暂不支持读配置
    _CLEAR_BIT(uiCmdKey, H_CTRL_RW);
    switch (uiCmdKey)
    {
        case CTRL_RCVBUF:
        {
            int *pRcvSize = (int *)pValue;
            iRet = setsockopt(pstCanLinuxCb->iFd, SOL_CAN_RAW, SO_RCVBUF, (char*)pRcvSize, sizeof(int));
            LOG_DBG(MOD_NAME, "can:%s set recv buf size.\n", pstCanLinuxCb->pName);
            break;
        }
        case CTRL_SNDBUF:
        {
            int *pSndSize = (int *)pValue;
            iRet = setsockopt(pstCanLinuxCb->iFd, SOL_CAN_RAW, SO_SNDBUF, (char*)pSndSize, sizeof(int));
            LOG_DBG(MOD_NAME, "can:%s set send buf size.\n", pstCanLinuxCb->pName);
            break;
        }
        case CTRL_CAN_FILTER:
        {
            VAR_ARRAY_S *pFilterArray = (VAR_ARRAY_S *)pValue;
            uint32_t *puiFilterCanID = (uint32_t *)(pFilterArray->ptr);
            struct can_filter astCanFilter[pFilterArray->uiLength];
            for (uint32_t i=0; i<pFilterArray->uiLength; i++)
            {
                astCanFilter[i].can_id = puiFilterCanID[i];
                astCanFilter[i].can_mask = CAN_SFF_MASK;
            }
            iRet = setsockopt(pstCanLinuxCb->iFd, SOL_CAN_RAW, CAN_RAW_FILTER, astCanFilter, sizeof(astCanFilter));
            LOG_DBG(MOD_NAME, "can:%s set filter list.\n", pstCanLinuxCb->pName);
            break;
        }
        default:
        {
            LOG_ERR(MOD_NAME, "can ctrl:%d no support.\n", uiCmdKey);
            return _ERR;
        }
    }
    return iRet;
}

static int32_t CAN_LockByLinux(hd_t hModuleDescriptor, uint32_t uiFlag)
{
    return _OK;
}

static int32_t CAN_UnlockByLinux(hd_t hModuleDescriptor, uint32_t uiFlag)
{
    return _OK;
}


#endif  // HAL_CAN == _ENABLE && HAL_PRODUCER == _HAL_LINUX_

/************************************** The End Of File **************************************/

